﻿#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>

//3.2 #define
//3.2.1 #define 定义标识符

//#define MAX 1000
//#define STR "hello bit"
//#define print printf("hehe\n")
//int main()
//{
//	//预处理阶段会删除#define MAX 1000，且将所有MAX都替换为1000
//	int m = MAX;
//	printf("%d\n", MAX);
//
//	printf("%s\n", STR);
//
//	print;
//	return 0;
//}

//#define CASE break;case //在写case语句的时候自动把 break写上。
//int main()
//{
//	//标准写法
//	switch (1)
//	{
//	case 1:
//		break;
//	case 2:
//		break;
//	case 3:
//		break;
//	case 4:
//		break;
//	}
//
//	//#define写法
//	switch (1)
//	{
//	case 1:
//	CASE 2:
//	CASE 3:
//	CASE 4:
//	}
//}

// 如果定义的 stuff过长，可以分成几行写，除了最后一行外，每行的后面都加一个反斜杠(续行符)。
//因为每次都要按下回车让内容去到下一行，所以续行符相当于转义了回车。（续行符后面不能有空格或其他内容，否则就不是转义回车）
//#define DEBUG_PRINT printf("file:%s\tline:%d\t \
//                          date:%s\ttime:%s\n" , \
//                          __FILE__,__LINE__ , \
//                          __DATE__,__TIME__ )



//3.2.2 #define 定义宏
//#define 机制包括了一个规定，允许把参数替换到文本中，这种实现通常称为宏（macro）或定义宏（define macro）。
//下面是宏的申明方式：
//#define name( parament-list ) stuff
//其中的 parament - list 是一个由逗号隔开的符号表，它们可能出现在stuff中

//注意：
//参数列表的左括号必须与name紧邻。
//如果两者之间有任何空白存在，参数列表就会被解释为stuff的一部分(如果有空白就变成符号，而不是宏)
//#define SQUARE1(X) X*X
////宏名SQUARE	 参数(X)	 宏体X*X
//
//#define SQUARE2(X) ((X)*(X))
////改为上方写法较为严谨，可以避免操作符优先级的影响
//int main()
//{
//	int r = SQUARE1(5);//求5的平方
//	printf("%d\n", r);
//
//	//上方宏设计有缺陷，示例如下：
//	int m = SQUARE1(5 + 1);
//	printf("%d\n", m);//11
//	//当写成SQUARE(5 + 1)这种有操作符形式时，并不是计算6的平方
//	//X为5+1，宏体X*X 为 5+1 * 5+1，宏只是替换
//
//	int n = SQUARE2(5 + 1);
//	printf("%d\n", n);//36
//	//尽量给参数和宏体的整体带上括号，避免操作符优先级的影响
//	return 0;
//}

//#define DOUBLE1(X) (X)+(X)
//
//#define DOUBLE2(X) ((X)+(X)) //宏体的整体加上括号更为严谨
//int main()
//{
//	int r = DOUBLE1(3);
//	printf("%d\n", r);//6
//
//	int m = 10 * DOUBLE1(3);
//	printf("%d\n", m);//33 结果并不是所期望的10*6=60
//	//替换后：10 * (3) + (3)，先计算10*3，再+3。所以要将宏体的整体加上括号
//
//	int n = 10 * DOUBLE2(3);
//	printf("%d\n", n);//60
//
//	return 0;
//}



//3.2.3 #define 替换规则
//在程序中扩展#define定义符号和宏时，需要涉及几个步骤。
//1. 在调用宏时，首先对参数进行检查，看看是否包含任何由#define定义的符号。如果是，它们首先被替换。
//2. 替换文本随后被插入到程序中原来文本的位置。对于宏，参数名被他们的值所替换。
//3. 最后，再次对结果文件进行扫描，看看它是否包含任何由#define定义的符号。如果是，就重复上述处理过程。
//
//#define M 100
//#define DOUBLE(X) ((X)+(X))
//int main()
//{
//	DOUBLE(M + 2);
//	//首先执行上述的第一个步骤，检查参数，将参数的定义符号替换
//	//即DOUBLE(100 + 2);
//	//然后执行上述的第二个步骤，
//	//即((100 + 2) + (100 + 2));
//
//	//注意：
//	//	1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏，不能出现递归。
//	//	2. 当预处理器搜索#define定义的符号的时候，字符串常量的内容并不被搜索。
//	
//	//2. 对于"M"；"DOUBLE(3)"这样的字符串不会替换，因为他们是字符串的内容
//	return 0;
//}



//3.2.4 #和##
//#用法
//void print(int n)
//{
//	printf("The value of n is %d\n", n);
//}
//
//#define PRINT(N) printf("the value of "#N" is %d\n", N)
////字符串内不能直接使用N，否则N就会被替换为变量名，语法错误
////#N就是让替换的变量名转换为字符串
////printf("the value of ""a"" is %d\n", a)
//#define PRINT2(N, FORMAT) printf("the value of "#N" is "FORMAT"\n", N)
//int main()
//{
//	printf("hello world\n");
//	printf("hello ""world\n");
//	//上方两种方式效果一样    
//
//	int a = 10;
//	printf("The value of a is %d\n", a);
//	int b = 20;
//	printf("The value of b is %d\n", b);
//	print(a);
//	//如果想要封装上方代码，函数做不到，因为字符串里面的内容(a,b)不能随着参数不同而变化
//
//	int a = 10;
//	PRINT(a);//the value of a is 10
//	int b = 20;
//	PRINT(b);//the value of b is 20
//
//	int c = 30;
//	PRINT2(c, "%d");//the value of c is 30
//	float d = 40.0f;
//	PRINT2(d, "%lf");//the value of d is 40.000000
//	return 0;
//}

//##用法
//#define CAT(Class, Num) Class##Num
//int main()
//{
//	int Class106 = 100;
//	printf("%d\n", CAT(Class, 106));
//	//##就是把两边的参数合并成一个符号，预处理后CAT(Class, 106)就替换成Class106
//	//注意：这样的连接必须产生一个合法的标识符。否则其结果就是未定义的。
//	return 0;
//}



//3.2.5 带副作用的宏参数
//当宏参数在宏的定义中出现超过一次的时候，如果参数带有副作用，那么你在使用这个宏的时候就可能出现危险，
//导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。
//#define MAX(x,y) ((x)>(y)?(x):(y))
//int main()
//{
//	//int a = 10;
//	//int b = a + 1;// b=11,a=10
//	//int b = ++a;  // b=11,a=11
//
//	//int m = MAX(2, 3);
//	//printf("%d\n", m);//3
//	
//	//带有副作用参数
//	int a = 5;
//	int b = 4;
//	int m = MAX(a++, b++);
//	//替换后
//	//int m = ((a++) > (b++) ? (a++) : (b++));
//	//先使用	     5   >   4
//	//后++      a=6     b=5
//	//因为5>4(表达式1)为真，表达式2计算，表达式2的结果是整个表达式的结果，即计算a++
//	//此时a=6，先使用后++。所以整个表达式的结果是6（即m=6），使用完后++，a=7。而表达式3(b++)没有机会计算
//
//	printf("m=%d\n", m);//m=6
//	printf("a=%d b=%d\n", a, b);//a=7 b=5
//	return 0;
//}



//3.2.6 宏和函数对比
//宏的形式
//#define MAX(x,y) ((x)>(y)?(x):(y))
//
//int Max(int x, int y)
//{
//	return (x > y ? x : y);
//}
//int main()
//{
//	//宏的优势：
//	//宏通常被应用于执行简单的运算，比如在两个数中找出较大的一个。那为什么不用函数来完成这个任务？
//	//1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。
//	//所以宏比函数在程序的规模和速度方面更胜一筹。
//	//2. 更为重要的是函数的参数必须声明为特定的类型。
//	//所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于 > 来比较的类型。
//	//宏是类型无关的。
//
//	//宏的缺点：当然和函数相比宏也有劣势的地方：
//	//1. 每次使用宏的时候，一份宏定义的代码将插入到程序中。除非宏比较短，否则可能大幅度增加程序的长度。
//	//2. 宏是没法调试的。
//	//3. 宏由于类型无关，也就不够严谨。
//	//4. 宏可能会带来运算符优先级的问题，导致程容易出现错。
//	return 0;
//}

//宏有时候可以做函数做不到的事情。比如：宏的参数可以出现类型，但是函数做不到。
//#define MALLOC(num, type) (type*)malloc((num)*sizeof(type))
//int main()
//{
//	//假设想用下面方式开辟空间(因为方便，可读性强)，但函数不支持，因为传参不能传类型
//	//malloc(10, int);
//	int* p = MALLOC((10), int);
//	//替换后 (int*)malloc((10)*sizeof(int))
//	return 0;
//}

//宏和函数的对比
//1. 代码长度：
//宏 - 每次使用时，宏代码都会被插入到程序中。除了非常小的宏之外，程序的长度会大幅度增长
//函数 - 函数代码只出现于一个地方；每次使用这个函数时，都调用那个地方的同一份代码
//2. 执行速度：
//宏 - 更快
//函数 - 存在函数的调用和返回的额外开销，所以相对慢一些
//3. 操作符优先级：
//宏 - 宏参数的求值是在所有周围表达式的上下文环境里，除非加上括号，否则邻近操作符的优先级可能会产生不可预料的后果，
//所以建议宏在书写的时候多些括号。
//函数 - 函数参数只在函数调用的时候求值一次，它的结果值传递给函数。表达式的求值结果更容易预测
//4. 带有副作用的参数：
//宏 - 参数可能被替换到宏体中的多个位置，所以带有副作用的参数求值可能会产生不可预料的结果
//函数 - 函数参数只在传参的时候求值一次，结果更容易控制
//5. 参数类型：
//宏 - 宏的参数与类型无关，只要对参数的操作是合法的，它就可以使用于任何参数类型
//函数 - 函数的参数是与类型有关的，如果参数的类型不同，就需要不同的函数，即使他们执行的任务是相同的。
//6. 调试：
//宏 - 宏是不方便调试的
//函数 - 函数是可以逐语句调试的
//7. 递归：
//宏 - 宏是不能递归的
//函数 - 函数是可以递归的



//3.2.7 命名约定
//一般来讲函数的宏的使用语法很相似。所以语言本身没法帮我们区分二者。
//那我们平时的一个习惯是：
//把宏名全部大写
//函数名不要全部大写



//3.3 #undef
//这条指令用于移除一个宏定义。
//#define M 100
//int main()
//{
//	printf("%d\n", M);
////#undef M //取消宏
//	//printf("%d\n", M);
//	return 0;
//}



//3.4 命令行定义
//许多C 的编译器提供了一种能力，允许在命令行中定义符号。用于启动编译过程。
//例如：当我们根据同一个源文件要编译出一个程序的不同版本的时候，这个特性有点用处。
//（假定某个程序中声明了一个某个长度的数组，如果机器内存有限，我们需要一个很小的数组，
//但是另外一个机器内存大些，我们需要一个数组能够大些。）
//int main()
//{
//	//允许我们在编译的时候给某些符号指定值
//  //gcc test.c -D SZ=10
//	int arr[SZ];
//	int i = 0;
//	for (i = 0; i < SZ; i++)
//	{
//		arr[i] = i;
//	}
//	for (i = 0; i < SZ; i++)
//	{
//		printf("%d ", arr[i]);
//	}
//	printf("\n");
//	return 0;
//}